package com.spzx.product.service.impl;

import com.spzx.common.core.utils.StringUtils;
import com.spzx.product.service.ITestService;
import com.spzx.product.util.TUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Author ChenXing
 * @Description 测试分布式锁
 * @Create 2024/8/10 11:46
 */

@Service
public class TestServiceImpl implements ITestService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


//    @Override
//    public  synchronized void  testLock() {
//
//        String num = stringRedisTemplate.opsForValue().get("num");
//
//        // 用于检查一个字符串是否不为空（不是null）且不只包含空白字符。
//        if(StringUtils.isNotBlank(num)){
//
//
//            Integer i = Integer.parseInt(num);
//            stringRedisTemplate.opsForValue().set("num",++i+"");
//
//
//        }
//
//    }

    @Override
    public void testLock() {

        System.out.println(Thread.currentThread().getName()+"正在测试testLock");

        String dflag = UUID.randomUUID().toString();// 删锁标记位
        // 设置锁（通俗理解为取号，只有取到号了才能执行下一步操作）
        Boolean lockName = stringRedisTemplate.opsForValue().setIfAbsent("lockName", dflag,9, TimeUnit.SECONDS);

        if (lockName) {

            try{
                String num = stringRedisTemplate.opsForValue().get("num");
                if (StringUtils.isNotBlank(num)) {
                    Integer i = Integer.parseInt(num);
                    stringRedisTemplate.opsForValue().set("num", ++i + "");
                }

            }finally {
                // 非常重要，拿到锁之后一定要考虑 释放锁，一定要记住
                // 实际工作当中，一定要保证能够释放锁，这种高危程序的逻辑一定要缜密
                // 此处，要将释放锁的代码放到 finally 当中
                // 如果有极端情况，拿到锁后还是不执行 finally 那么就要设置过期时间，保证万无一失

                // 但是设置过期时间后，又给了别人拿到锁和修改锁的机会，过期时间和解锁时间有冲突
                // 解决办法 ： 设置锁的颗粒度，确保是自己的那把锁

                // 最后，判断完是自己的锁后，要立刻释放，两步有原子性，此处，用 lua 脚本保证（判断和过期同时发生，会误删,并发低时可以不考虑，并发高时，不能忽略）

                if(dflag.equals(stringRedisTemplate.opsForValue().get("lockName"))){
                    stringRedisTemplate.delete("lockName");

                    String delLua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                    redisScript.setScriptText(delLua);
                    redisScript.setResultType(Long.class);
                    stringRedisTemplate.execute(redisScript, Arrays.asList("lockName"), dflag);
                }


            }



        }else{

            // 如果没有拿到锁，不要放跑了,进行内部无限循环，直到拿到锁为止（自旋）
            TUtil.sleep(100);
            testLock();


        }



    }
}
